﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;

namespace DHQGHN.Services.QTHT
{
    public class PasswordGenerator : IDisposable
    {
        log4net.ILog logger = log4net.LogManager.GetLogger(typeof(PasswordGenerator));

        private const int DefaultMinimum = 6;
        private const int DefaultMaximum = 10;
        private const int UBoundDigit = 61;

        private RNGCryptoServiceProvider rng;
        private int minSize;
        private int maxSize;
        private bool hasRepeating;
        private bool hasConsecutive;
        private bool hasSymbols;
        private string exclusionSet;
        private char[] pwdCharArray;

        public PasswordGenerator()
        {
            this.Minimum = DefaultMinimum;
            this.Maximum = DefaultMaximum;
            this.ConsecutiveCharacters = false;
            this.RepeatCharacters = true;
            this.ExcludeSymbols = false;
            this.Exclusions = null;

            this.rng = new RNGCryptoServiceProvider();
        }

        protected int GetCryptographicRandomNumber(int lBound, int uBound)
        {
            // Assumes lBound >= 0 && lBound < uBound
            // returns an int >= lBound and < uBound
            try
            {
                uint urndnum;
                byte[] rndnum = new Byte[4];
                if (lBound == uBound - 1)
                {
                    // test for degenerate case where only lBound can be returned
                    return lBound;
                }

                uint xcludeRndBase = (uint.MaxValue -
                    (uint.MaxValue % (uint)(uBound - lBound)));

                do
                {
                    rng.GetBytes(rndnum);
                    urndnum = System.BitConverter.ToUInt32(rndnum, 0);
                } while (urndnum >= xcludeRndBase);

                return (int)(urndnum % (uBound - lBound)) + lBound;
            }
            catch (Exception ex)
            {
                logger.Error(ex.ToString());
                throw;
            }
        }

        protected char GetRandomCharacter()
        {
            try
            {
                int upperBound = pwdCharArray.GetUpperBound(0);

                if (true == this.ExcludeSymbols)
                {
                    upperBound = PasswordGenerator.UBoundDigit;
                }

                int randomCharPosition = GetCryptographicRandomNumber(
                    pwdCharArray.GetLowerBound(0), upperBound);

                char randomChar = pwdCharArray[randomCharPosition];

                return randomChar;
            }
            catch (Exception ex)
            {
                logger.Error(ex.ToString());
                throw;
            }
        }

        public string Generate()
        {
            try
            {
                // Pick random length between minimum and maximum   
                int pwdLength = GetCryptographicRandomNumber(this.Minimum,
                    this.Maximum);

                StringBuilder pwdBuffer = new StringBuilder();
                pwdBuffer.Capacity = this.Maximum;

                // Generate random characters
                char lastCharacter, nextCharacter;

                // Initial dummy character flag
                lastCharacter = '\n';
                nextCharacter = '\n';

                for (int i = 0; i < pwdLength; i++)
                {
                    nextCharacter = GetRandomCharacter();

                    if (false == this.ConsecutiveCharacters)
                    {
                        while (lastCharacter == nextCharacter)
                        {
                            nextCharacter = GetRandomCharacter();
                        }
                    }

                    if (false == this.RepeatCharacters)
                    {
                        string temp = pwdBuffer.ToString();
                        int duplicateIndex = temp.IndexOf(nextCharacter);
                        while (-1 != duplicateIndex)
                        {
                            nextCharacter = GetRandomCharacter();
                            duplicateIndex = temp.IndexOf(nextCharacter);
                        }
                    }

                    if ((null != this.Exclusions))
                    {
                        while (-1 != this.Exclusions.IndexOf(nextCharacter))
                        {
                            nextCharacter = GetRandomCharacter();
                        }
                    }

                    pwdBuffer.Append(nextCharacter);
                    lastCharacter = nextCharacter;
                }

                Dispose();

                if (null != pwdBuffer)
                {
                    return pwdBuffer.ToString();
                }
                else
                {
                    return String.Empty;
                }
            }
            catch (Exception ex)
            {
                logger.Error(ex.ToString());
                throw;
            }
        }

        public string Exclusions
        {
            get { return this.exclusionSet; }
            set { this.exclusionSet = value; }
        }

        public int Minimum
        {
            get { return this.minSize; }
            set
            {
                this.minSize = value;
                if (PasswordGenerator.DefaultMinimum > this.minSize)
                {
                    this.minSize = PasswordGenerator.DefaultMinimum;
                }
            }
        }

        public int Maximum
        {
            get { return this.maxSize; }
            set
            {
                this.maxSize = value;
                if (this.minSize >= this.maxSize)
                {
                    this.maxSize = PasswordGenerator.DefaultMaximum;
                }
            }
        }

        public char[] Kytuso
        {
            get { return this.pwdCharArray; }
            set { this.pwdCharArray = value; }
        }

        public bool ExcludeSymbols
        {
            get { return this.hasSymbols; }
            set { this.hasSymbols = value; }
        }

        public bool RepeatCharacters
        {
            get { return this.hasRepeating; }
            set { this.hasRepeating = value; }
        }

        public bool ConsecutiveCharacters
        {
            get { return this.hasConsecutive; }
            set { this.hasConsecutive = value; }
        }
        public void Dispose()
        {
            this.rng.Dispose();
        }

    }
}